package com.gitee.yongzhuzl.commonutil.util.redis.lock;

import com.gitee.yongzhuzl.commonutil.util.empty.EmptyUtil;
import com.gitee.yongzhuzl.commonutil.util.equals.EqualsUtil;
import com.gitee.yongzhuzl.commonutil.util.redis.boot.RedisTemplateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * description: redis分布式锁
 * @author 朱正磊
 * @date 2019-12-03 10:13:21
 */
@Component
public class RedisDcsLock {

    private static final Logger log = LoggerFactory.getLogger(RedisDcsLock.class);

    @Resource
    private RedisTemplateUtil redisTemplateUtil;

    /**
     * description: 加锁
     * @author 朱正磊
     * @date 2019-12-03 10:36:31
     * @param key 键
     * @param value 值：当前时间 + 超时时间
     * @return 如果加锁成功返回true，否则返回false
     */
    public Boolean lock(String key, String value) {
        // 判断是否已经有线程加了锁，如果没有加锁，那么进行加锁，同时返回true，否则进行下一步
        if (redisTemplateUtil.setIfAbsent(key, value)) {
            // 加锁成功，放行业务逻辑代码
            return true;
        }
        // 利用超时时间解决死锁的问题
        // 假如当前线程currentValue=A，这时同时来了两个线程，这两个线程的value都是B
        String currentValue = redisTemplateUtil.get(key, String.class);
        // 如果锁已经过期（currentValue小于当前时间）
        if (currentValue != null && Long.parseLong(currentValue) < System.currentTimeMillis()) {
            // 获取上一个锁，同时加新的锁
            // getAndSet方法一次只允许一个线程去执行，多线程执行该方法存在先后顺序，即体现了redis的单线程原理
            // 也就是说多线程的情况下也只会有一个线程拿到锁
            String oldValue = redisTemplateUtil.getAndSet(key, value, String.class);
            // 第一个线程进来的时候oldValue=currentValue(A=A)，
            // 第二个线程进来的时候oldValue!=currentValue(B!=A)
            // 如果加锁成功，放行业务逻辑代码
            return EmptyUtil.isNotEmpty(oldValue) && EqualsUtil.isEquals(oldValue, currentValue);
        }
        // 加锁失败，不放行业务逻辑代码
        return false;
    }

    /**
     * description: 解锁
     * @author 朱正磊
     * @date 2019-12-03 10:36:39
     * @param key 键
     * @param value 值
     */
    public void unlock(String key, String value) {
        try {
            // 获取当前锁
            String currentValue = redisTemplateUtil.get(key, String.class);
            // 如果入参就是当前锁
            if (EmptyUtil.isNotEmpty(currentValue) && EqualsUtil.isEquals(currentValue, value)) {
                // 删除当前锁，即进行解锁
                redisTemplateUtil.del(key);
            }
        } catch (Exception e) {
            log.error("【redis分布式锁】解锁异常", e);
        }
    }

}
